package application

import (
	"fmt"
	log "github.com/sirupsen/logrus"
	"sync"
	"time"
	"usbipproxy/common"
	"usbipproxy/domain"
	"usbipproxy/domain/service"
	"usbipproxy/domain/tcpclient"
	"usbipproxy/pb"
)

/*
type ReqCtx struct {
	Notify chan struct{}
	Data   *pb.ResponseParam
}
*/

type TcpClientServiceImpl struct {
	usbIpType         int
	eth               string
	reConnectInterval int
	tcpChannel        string
	closed            bool
	tcpMsgHandle      TcpMsgHandleService
	msgNotifyService  service.MsgNotifyService
	quit              chan struct{}
	requests          map[string]chan *pb.ResponseParam
	rwLock            sync.RWMutex
}

func (cli *TcpClientServiceImpl) SendOutMsgByDispatchIdWithNoReply(msg *pb.ProxyMsg, dispatchId string) error {
	//TODO implement me
	panic("implement me")
}

func (cli *TcpClientServiceImpl) SendOutMsgByDispatchId(msg *pb.ProxyMsg, dispatchId string) (*pb.ResponseParam, error) {
	//TODO implement me
	panic("implement me")
}

func (cli *TcpClientServiceImpl) RecvInMsgByDispatchId(msg *pb.ProxyMsg, dispatchId string) error {
	//TODO implement me
	panic("implement me")
}

func NewTcpClientServiceImpl(eth, tcpChannel string, usbIpType, reConnectInterval int,
	tcpMsgHandle TcpMsgHandleService, msgNotifyService service.MsgNotifyService) TcpClientService {
	impl := &TcpClientServiceImpl{
		usbIpType:         usbIpType,
		eth:               eth,
		reConnectInterval: reConnectInterval,
		tcpChannel:        tcpChannel,
		tcpMsgHandle:      tcpMsgHandle,
		quit:              make(chan struct{}, 1),
		msgNotifyService:  msgNotifyService,
		requests:          make(map[string]chan *pb.ResponseParam),
	}

	return impl
}

func (cli *TcpClientServiceImpl) SendOutMsg(msg *pb.ProxyMsg) (*pb.ResponseParam, error) {
	respChan := make(chan *pb.ResponseParam, 1)
	cli.rwLock.Lock()
	cli.requests[msg.GetBase().GetReqId()] = respChan
	cli.rwLock.Unlock()
	cli.msgNotifyService.SendMsg(msg)

	// 等待响应或超时
	select {
	case resp := <-respChan:
		log.Infof("TcpClientServiceImpl SendMsg recv rsp %v", msg.GetBase().GetReqId())
		return resp, nil
	case <-time.After(common.ResponseTimeout * time.Second):
		cli.removeRequest(msg.GetBase().GetReqId())
		log.Infof("TcpClientServiceImpl req:%v SendMsg timeout", msg.GetBase().GetReqId())
		return nil, fmt.Errorf("request:%v timeout", msg.GetBase().GetReqId())
	}
}

func (cli *TcpClientServiceImpl) RecvInMsg(msg *pb.ProxyMsg) error {
	cli.msgNotifyService.RecvMsg(msg)
	return nil
}

func (cli *TcpClientServiceImpl) removeRequest(requestID string) {
	cli.rwLock.Lock()
	delete(cli.requests, requestID)
	cli.rwLock.Unlock()
}

func (cli *TcpClientServiceImpl) Stop() error {
	if cli.closed {
		return nil
	}
	cli.closed = true
	close(cli.quit)
	return nil
}

func (cli *TcpClientServiceImpl) getMac() string {
	var mac string
	if cli.eth != "" {
		mac, _ = common.GetMACByName(cli.eth)
	}

	if mac == "" {
		macList, _ := common.GetAllMac()
		if len(macList) > 0 {
			mac = macList[0]
		}
	}

	return mac
}

func (cli *TcpClientServiceImpl) Start() error {

	for !cli.closed {
		interval, mac := cli.reConnectInterval, cli.getMac()
		log.Infof("try to connect to %s", cli.tcpChannel)
		msgPipe := domain.NewMsgPipe()
		connId, _ := common.Snowflake.GetSnowflakeId()
		conn := tcpclient.NewTcpConn(connId, cli.tcpChannel, msgPipe)
		user := domain.NewDispatchClient(mac, pb.UsbIpType(cli.usbIpType), conn)
		go conn.Start()
		cli.handRecvAndSend(user)
		conn.Stop()
		time.Sleep(time.Duration(interval) * time.Second)
	}

	return nil
}

func (cli *TcpClientServiceImpl) handRecvAndSend(user *domain.DispatchClient) {
LOOP:
	for {
		select {
		case <-cli.quit:
			break LOOP
		case msg := <-user.GetSessRepo().GetInMsg():
			if msg == nil {
				log.Infof("TcpClientServiceImpl handRecvAndSend msg is nil")
				break LOOP
			}
			if msg.GetMsgType() == pb.MsgType_RESPONSE {
				cli.rwLock.RLock()
				rspChan := cli.requests[msg.GetBase().GetReqId()]
				cli.rwLock.RUnlock()

				if rspChan != nil {
					select {
					case rspChan <- msg.GetResponseParam():
					default:
						log.Warnf("response channel has been sended or closed")
					}

				}
			} else {
				cli.tcpMsgHandle.Handle(msg, user)
			}
			//user.SetHeartUtc(time.Now().Unix())
		case msg := <-cli.msgNotifyService.GetOutMsgChan():
			user.UpdateBaseParamForOut(msg)
			user.GetSessRepo().SendOutMsg(msg)
		case msg := <-cli.msgNotifyService.GetInMsgChan():
			user.GetSessRepo().RecvInMsg(msg)
		}
	}

	log.Infof("handle msg over")
}
